feat(i18n): localize remaining hardcoded UI surfaces#9082
Conversation
|
Important Review skippedToo many files! This PR contains 300 files, which is 150 over the limit of 150. To get a review, narrow the scope: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (300)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Localize remaining hardcoded UI strings across web, space, editor, and shared surfaces using the existing react-i18next namespace JSON files introduced by makeplane#8898. Add the new keys to the relevant auth, common, editor, empty-state, inbox, page, project, work-item, and workspace locale files across all supported locales. Co-authored-by: Codex <noreply@openai.com>
0ca8de5 to
03c1a8d
Compare
|
@coderabbitai you can now review the entire change. (Skip .json files) |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 4774 🏁 Script executed: Length of output: 1 🏁 Script executed: Length of output: 203988
🔍 Code Review🐛 Issues / Potential Regressions1. The -function InputView({ label, value, placeholder, onChange, autoFocus }: InputViewProps) {
+function InputView({ label, value, placeholder, onChange }: InputViewProps) {
...
- autoFocus={autoFocus}Suggestion: Keep 2. Both 3. Duplicate The exact same constant is defined in two files:
Consider extracting it to a shared location (e.g. a 4. Hardcoded English defaults in // packages/propel/src/spinners/circular-spinner.tsx
export function Spinner({ label = "Loading..." }: ISpinner)
// packages/propel/src/banner/banner.tsx
dismissLabel = "Dismiss banner",These propel-level components are locale-agnostic — the defaults will always render English regardless of the active locale. Since callers already hold a 5. Fragile sentence-splitting for Terms & Conditions ( {isSignUp ? t("space_public.by_creating_account") : t("space_public.by_signing_in")},{" "}
{t("space_public.you_agree_to_our")}Splitting a single grammatical sentence across three separate keys is fragile for languages that have different word order or punctuation rules. A better pattern is to use a single key with an interpolated variable and render the link separately, or use ✅ Noteworthy Improvements Bundled InThese are positive changes worth acknowledging:
💬 Minor Notes
|
Restore initial focus for the localized link editor and filter panels without using the autoFocus attribute, keeping the keyboard-first interaction while satisfying the a11y lint rules. Co-authored-by: Codex <noreply@openai.com>
Tighten a small batch of locale strings against the English source for command-style actions, work item terminology, and workspace invitation messaging. Co-authored-by: Codex <noreply@openai.com>
|
@coderabbitai review the latest changes. Skip .json files. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 207639
🔍 Code Review✅ Previous Issues AddressedAll the major items flagged in the last review have been resolved:
🐛 Remaining / New Issues1. Duplicate The identical constant is still defined in both:
This was flagged in the previous review and is still open. Consider extracting it to a shared 2. // packages/propel/src/spinners/circular-spinner.tsx
label = "Loading..."
// packages/propel/src/banner/banner.tsx
dismissLabel = "Dismiss banner",Both props are now properly wired (good progress), but the hardcoded English defaults remain. Callers that don't pass a translated string will still silently display English text. Since propel is locale-agnostic, these defaults should ideally be documented as "callers are required to pass a translated string," or the props should be made required. 3. Fragile Terms & Conditions string splitting (still present) // apps/space/components/account/terms-and-conditions.tsx
{isSignUp ? t("space_public.by_creating_account") : t("space_public.by_signing_in")},{" "}
{t("space_public.you_agree_to_our")}This is still the same split-key pattern. Languages with different word order or punctuation will produce unnatural output. The 4. In three files, a new // apps/space/components/issues/issue-layouts/kanban/default.tsx
const groupList = getGroupByColumns(..., {
allWorkItems: `${t("common.all")} ${t("work_items")}`,
none: t("common.none"),
});
// apps/space/components/issues/issue-layouts/kanban/swimlanes.tsx
const localizedLabels = {
allWorkItems: `${t("common.all")} ${t("work_items")}`,
none: t("common.none"),
};
// apps/space/components/issues/issue-layouts/list/default.tsx
const groupList = getGroupByColumns(..., {
allWorkItems: `${t("common.all")} ${t("work_items")}`,
none: t("common.none"),
});Since const localizedLabels = useMemo(() => ({
allWorkItems: `${t("common.all")} ${t("work_items")}`,
none: t("common.none"),
}), [t]);5. In archive: async (_workspaceSlug: string, _projectId: string, issueId: string) => {
try {
await archiveIssue(_workspaceSlug, _projectId, issueId);
} catch (error) { ... }
},The convention of 💬 Minor Notes
|
|
@codex review the latest changes. Skip .json files. |
|
@claude review the latest changes. Skip .json files. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae52e18462
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <button | ||
| type="button" | ||
| key={invitation.id} | ||
| className={`flex cursor-pointer items-center gap-2 rounded-lg border border-subtle px-3 py-2 hover:bg-surface-2`} | ||
| className={`flex w-full cursor-pointer items-center gap-2 rounded-lg border border-subtle px-3 py-2 text-left hover:bg-surface-2`} |
There was a problem hiding this comment.
Keep invitation row non-button when rendering checkbox
This row was switched to a <button> while it still contains <Checkbox />, and Checkbox renders an actual <input type="checkbox">. Nesting one interactive control inside another is invalid HTML and causes inconsistent keyboard/screen-reader behavior (the row and checkbox become competing controls), so invitation selection can become unreliable for assistive-tech users. Keep the row as a non-button container with explicit click/keyboard handlers, or render a non-interactive check indicator inside the button.
Useful? React with 👍 / 👎.
|
@coderabbitai I reviewed the remaining items.\n\n- : agreed the / names are misleading since they are used. I am treating that as a naming cleanup, not a behavioral issue for this PR.\n- : the current flow is and only then . With normal async semantics, the redirect does not run if the delete call rejects, so I am not treating this as a confirmed regression from the current code.\n- The duplicated date filter map, / English defaults, and the split Terms & Conditions sentence are valid follow-up cleanup ideas, but they are outside the scope of this i18n PR.\n\nI am keeping this PR focused on localized strings and the minimum code changes required to preserve upstream behavior. |
|
@coderabbitai I reviewed the remaining items.
I am keeping this PR focused on localized strings and the minimum code changes required to preserve upstream behavior. |
|
Tip For best results, initiate chat on the files or code changes.
Would you like me to open follow-up GitHub issues for the three deferred items so they don't get lost? I can assign them to you and backlink this PR. |
|
Tip For best results, initiate chat on the files or code changes.
Would you like me to open GitHub issues for the three deferred items so they don't get lost between PRs? I can create them now and backlink this PR. |
|
@coderabbitai Thanks. Please do not open follow-up issues automatically for now. Reasoning:
I will track those separately later if they still matter after merge. |
|
Tip For best results, initiate chat on the files or code changes.
🐇 。 ✿ ✏️ Learnings added
|
Co-authored-by: Codex <noreply@openai.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e88539e4e1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| title: t("space_auth.sign_out.toast.error.title"), | ||
| message: t("space_auth.sign_out.toast.error.message"), |
There was a problem hiding this comment.
Use existing auth sign-out error translation keys
When signOut() fails, this toast resolves space_auth.sign_out.toast.error.*, but the locale files define those messages under auth.sign_out.toast.error.* (for example in packages/i18n/src/locales/en/auth.json) and do not include a space_auth.sign_out branch. In that failure path, users will see untranslated key strings instead of a readable localized error.
Useful? React with 👍 / 👎.
Co-authored-by: Codex <noreply@openai.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da3874f697
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| useEffect(() => { | ||
| searchInputRef.current?.focus(); | ||
| }, []); |
There was a problem hiding this comment.
Keep mobile guard before focusing filter search input
This effect now focuses the search field every time the filter panel mounts, but the previous implementation explicitly skipped autofocus on mobile (autoFocus={!isMobile}). On phones/tablets this will immediately open the soft keyboard when the filter UI opens, obscuring the options list and degrading filter selection flow; the focus call should stay conditional for non-mobile contexts.
Useful? React with 👍 / 👎.
- Replace split-key TermsAndConditions with Trans component so sentence structure stays correct in all 18 supported languages; add composite terms_sign_up / terms_sign_in keys to every auth.json - Export Trans from @plane/i18n for consistent import convention - Restore isMobile guard in InboxIssueFilterSelection useEffect so mobile users are not unexpectedly focused on the search input - Deduplicate PAST_DURATION_FILTER_I18N_KEYS into a shared inbox-filter/constants.ts; both filter and applied-filter date components now import from the single source of truth https://claude.ai/code/session_01CLFmPiRwMYkD6BeSZxhE6B
Apply 信达雅 (faithfulness/expressiveness/elegance) review findings from
multi-agent professional translation audit:
**Critical fixes (high severity):**
- zh-CN/zh-TW: Fix "good"+"morning" concatenation bug ("早上早上"/"早安早上"
→ "早上好"/"早安") by emptying `good` and moving full greeting to time keys
- zh-CN/zh-TW: Fix "Intake Trends" mistranslated as "入学趋势" (enrollment
trends) → "需求收集趋势/趨勢" (intake/requirements trends)
- zh-CN: Fix `onboarding.profile_setup_failed` "配置文件设置失败"
→ "个人资料设置失败" ("config file" vs "profile")
- zh-CN: Fix snooze "暂停" (pause) → "延后" (defer/postpone)
- ja: Fix severe phonetic transliterations back to Japanese
("クリエイテッド アット"→"作成日時", "セービング"→"保存中", etc.)
- ru: Fix "Пейдж" (wrong transliteration) → "Страница" for `common.page`
- ru: Fix gender agreement "Новое пароль" → "Новый пароль"
- ru: Fix spelling "высококонтрастностная" → "высококонтрастная"
- de: Fix typo "Däschbords" → "Dashboards"
- sk: Fix verbal aspect error "Prepínať" (imperfective) → "Prepnúť"
- sk: Fix "Nahranie diela" (artwork upload) → "Nahranie súboru" (file upload)
- pl: Fix grammar "Zanim" (conjunction) → "Przed" (preposition) for date filter
- tr-TR/es: Fix placeholder "alır-atar-uçar"/"obtiene-establece-vuela" → "123456"
- fr/it/ua/tr-TR: Fix ' HTML entities in JSON strings (72 instances total)
- zh-TW: Fix "文件" (document) → "檔案" (file) in file_upload, fix "字段"→"欄位"
- zh-TW: Fix "技術支援" (tech support) → "Powered by" for space_public.powered_by
- zh-TW: Fix "選修的" (elective course) → "選填" (optional field)
**Structural fixes:**
- All 18 languages: Translate 6 untranslated keys that remained as English
(completed_on, your_profile, developer, work_structure, execution, administration)
- All 18 languages: Fix `common.order_by.manual` removing spurious "- Ранг/ランク"
additions not present in English source
- All 18 languages: Fix `pi_chat` from "AI Chat" variants → "Plane AI" (brand name)
- All 18 languages: Fix `attachmentComponent.uploader.drag_and_drop` - restore
missing "or click to upload" option (was truncated to drag-only in 14 languages)
- All 18 languages: Remove hardcoded `\n` from `issue.archive.description`
**Plural/grammar fixes:**
- ru/cs/pl/sk: Fix `inbox.sidebar.label_count` with proper ICU plural rules
- ru: Fix `inbox_issue.snoozed.description` missing "few" plural category
- ru: Fix `space_auth.resend_in` trailing English "s" → Russian "с"
- ua: Fix `space_auth.resend_in` and `go_to_workspace` form consistency
- ua: Fix `space_auth.go_to_workspace` imperative → infinitive
**Consistency fixes:**
- zh-TW workspace.json: Replace "創建"→"建立", "工作空間"→"工作區" throughout
- cs/editor.json: Fix "Centrum"→"Střed" for alignment label, fix italic case
- pl/editor.json: Unify alignment labels (left/center/right), fix insert_left
- ru/editor.json: Fix `editor.menu_items.quote` verb→noun form
- ru/work-item.json: Fix `issue.sibling.label` "related"→"sibling" semantics
- ro/auth.json: Fix missing diacritics (Continua→Continuă)
- it/inbox.json: Fix snooze action label
- ko: Fix greetings to natural Korean (안녕하세요 pattern)
- ja/auth.json: Fix resend_in "{seconds}s" → "{seconds}秒後に再送信"
- ko/auth.json: Fix resend_in "{seconds}s" → "{seconds}초 후에 재전송"
https://claude.ai/code/session_01CLFmPiRwMYkD6BeSZxhE6B
0ca95a0 to
3af7f0f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3af7f0fd3c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <button | ||
| type="button" | ||
| className="inline-flex cursor-pointer items-center gap-1.5" | ||
| onClick={() => setCreateMore((prevData) => !prevData)} | ||
| role="button" | ||
| tabIndex={getIndex("create_more")} | ||
| > | ||
| <ToggleSwitch value={createMore} onChange={() => {}} size="sm" /> |
There was a problem hiding this comment.
Avoid nesting ToggleSwitch inside a button
Wrapping ToggleSwitch with a native <button> creates a button-inside-button structure, because @plane/ui implements ToggleSwitch with Headless UI Switch (which renders a button). In this modal footer, that invalid interactive nesting can cause duplicate/competing click and keyboard behavior (especially Enter/Space activation and screen-reader control semantics) when users toggle create more. Keep the wrapper non-interactive (or move click handling onto the switch/label) instead of using a parent button.
Useful? React with 👍 / 👎.
55562d8 to
5f9662f
Compare
Co-authored-by: Codex <noreply@openai.com>
5f9662f to
1e9dbc8
Compare
Apply a targeted pass over Gemini-reviewed locale findings, focusing on clear mistranslations, grammatical case issues, editor terminology, count labels, date filters, and product terminology consistency across the PR locale updates. Co-authored-by: Codex <noreply@openai.com>
|
Follow-up translation QA pass applied in What changed:
Validation:
|
Apply another targeted pass over Gemini-reviewed translation findings that remained clearly actionable, including UI command wording, workspace terminology, instance labels, validation messages, and product term consistency. Co-authored-by: Codex <noreply@openai.com>
|
Added a second follow-up translation QA commit: What changed in this pass:
Validation:
|
Apply follow-up translation QA findings across locales, focusing on user-facing clarity, Plane product terminology, locale-specific wording, and glossary consistency. Co-authored-by: Codex <noreply@openai.com>
|
Added another translation QA follow-up commit: What changed:
Validation:
|
|
Hi @sriramveeraghanta, could you please review this PR when you have a chance? This is a community localization contribution against the preview branch, and it is ready for maintainer review. Thank you. |
|
@xuhongbo Thank you for your contribution. Team will review the PR and merge it. |
Apply another round of translation refinements across supported non-English locales after multi-model review against the English source strings. The review pass used qwen3.6-flash for full locale/namespace candidate generation, deepseek-v4-flash for candidate validation, gemini-3.1-pro-preview via Gemini CLI for per-locale review, and gpt-5.3-codex-spark plus Claude Code for final cross-checking. Pure style-only suggestions were filtered out and greeting strings were checked against their actual runtime composition before keeping changes. Validation: pnpm --filter=@plane/i18n run check:sync; full i18next-icu render scan across 18 locales and 28 namespaces; structural token scan with expected plural-suffix adaptations; git diff --check. Co-authored-by: Codex <noreply@openai.com>
|
@sriramveeraghanta I pushed an additional localization quality pass in Translation QA ReportScopeThis follow-up pass reviewed the non-English locale files against the English source strings again, focusing on user-facing correctness rather than broad stylistic rewrites. The goal was to catch translations that could affect product comprehension in Plane's project-management UI: work items, cycles, modules, inbox/intake flows, workspace settings, authentication, integrations, templates, and analytics surfaces. Review ProcessI used a multi-model review pipeline so that we did not rely on a single model's translation preference:
Selection CriteriaI only kept suggestions that met at least one of these criteria:
I rejected pure style preferences and also rejected model suggestions that looked reasonable in isolation but did not match the actual runtime context. For example, the greeting strings are composed in code as ValidationAfter applying the selected changes, I ran:
No review scripts or intermediate model outputs are included in the commit. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4d703f189
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| )} | ||
| <span className="break-words text-secondary">{row.original.display_name ?? t(`Unassigned`)}</span> | ||
| <span className="break-words text-secondary"> | ||
| {row.original.display_name ?? t("common.unassigned")} |
There was a problem hiding this comment.
Use the defined unassigned translation key
t("common.unassigned") does not resolve to any locale entry, so rows without display_name will render the raw key string instead of a label. In packages/i18n/src/locales/en/common.json, the existing key is "unassigned" (not nested under common), so this new lookup regresses the assignee fallback text in the insight table.
Useful? React with 👍 / 👎.
Use the existing top-level unassigned key for the work items insight table fallback label. Validation: pnpm --filter=@plane/i18n run check:sync; git diff --check. Co-authored-by: Codex <noreply@openai.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ccb7e50f4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| isChecked={isCustomDateSelected()} | ||
| onClick={handleCustomDate} | ||
| title="Custom" | ||
| title={t("custom")} |
There was a problem hiding this comment.
Use a date-specific key for custom date option
This replaces the literal "Custom" with t("custom"), but in en/common.json that key is "Custom theme", so the custom-date filter option now shows unrelated copy in the inbox filter UI. This is a user-visible regression whenever the date filter menu is opened; use an inbox/date-specific translation key instead of the global custom key.
Useful? React with 👍 / 👎.
Description
Closes #9089.
Expands the existing localization coverage by routing UI surfaces that still used hardcoded English through the current i18n namespace JSON system.
Supersedes #9069 and rebases the hardcoded-UI localization work on top of the current preview branch after #8898. This version follows the new react-i18next namespace JSON structure from #8898 instead of adding a catch-all namespace. New strings are added to the relevant existing namespace files: auth, common, editor, empty-state, inbox, page, project, work-item, and workspace.
Scope:
Baseline measurement:
Translation quality notes:
Type of Change
Screenshots and Media (if applicable)
Not included. This is a text/i18n wiring change; affected screens keep the same layout and behavior while rendering localized copy.
Test Scenarios
pnpm --filter=@plane/i18n run check:syncpnpm fix:formatpnpm turbo run check:lint --filter=@plane/i18n --filter=web --filter=space --filter=@plane/editor --filter=@plane/propelpnpm turbo run check:types --filter=@plane/i18n --filter=web --filter=space --filter=@plane/editor --filter=@plane/propelReferences